#include "StdAfx.h"

static ID3DXLine* gpLine = 0;

M2Model::M2Model(std::string szFile, ADTModel* pMap, ui32 spawn) : m_fileName(szFile), ModelBase(0), m_pSpawnMap(pMap),
m_spawnEntry(spawn)
{
	translation[0] = translation[1] = translation[2] = 0.0f;
	rotation[0] = rotation[1] = rotation[2] = 0.0f;
	scaling[0] = scaling[1] = scaling[2] = 1.0f;
	m_selected = false;
	if(gpLine == 0)
		D3DXCreateLine(sD3D, &gpLine);
}

void M2Model::LoadCached(M2ModelCache* cache)
{
	for(ui32 i = 0; i < cache->nPasses; ++i)
		m_textures[i] = CTextureHandler::GetInstance()->GetTexture(cache->textures[i]);
	m_meshes = new Mesh[cache->nPasses];
	for(ui32 i = 0; i < cache->nPasses; ++i)
		m_meshes[i].Load(cache->vertices[i], VERTEX3FT, cache->nVertices[i], cache->nVertices[i] / 3);
	m_nMeshes = cache->nPasses;
	sD3D.AddMesh(this);
}

bool M2Model::Init(bool noCache)
{
	m_meshes = 0;
	CMpqFile file(m_fileName);
	if(!file.IsOpened())
		return false;

	file.SetInc(0);
	file.Read(0, sizeof(M2FileHeader), &m_header);
	ui32 j = 0;
	float xl = m_header.vertexBox[0].x;
	float yl = m_header.vertexBox[0].z;
	float zl = m_header.vertexBox[0].y;
	float xh = m_header.vertexBox[1].x;
	float yh = m_header.vertexBox[1].z;
	float zh = m_header.vertexBox[1].y;

	m_boundingBox[0].x = xl;	m_boundingBox[0].y = yl;	m_boundingBox[0].z = zl;
	m_boundingBox[1].x = xh;	m_boundingBox[1].y = yl;	m_boundingBox[1].z = zl;
	m_boundingBox[2].x = xh;	m_boundingBox[2].y = yh;	m_boundingBox[2].z = zl;
	m_boundingBox[3].x = xl;	m_boundingBox[3].y = yh;	m_boundingBox[3].z = zl;
	m_boundingBox[4] = m_boundingBox[0];
	m_boundingBox[5].x = xl;	m_boundingBox[5].y = yl;	m_boundingBox[5].z = zh;
	m_boundingBox[6].x = xl;	m_boundingBox[6].y = yh;	m_boundingBox[6].z = zh;
	m_boundingBox[7] = m_boundingBox[3];
	m_boundingBox[8] = m_boundingBox[2];
	m_boundingBox[9].x = xh;	m_boundingBox[9].y = yh;	m_boundingBox[9].z = zh;
	m_boundingBox[10] = m_boundingBox[6];
	m_boundingBox[11] = m_boundingBox[5];
	m_boundingBox[12].x = xh;	m_boundingBox[12].y = yl;	m_boundingBox[12].z = zh;
	m_boundingBox[13].x = xh;	m_boundingBox[13].y = yl;	m_boundingBox[13].z = zl;
	m_boundingBox[14].x = xh;	m_boundingBox[14].y = yl;	m_boundingBox[14].z = zh;
	m_boundingBox[15].x = xh;	m_boundingBox[15].y = yh;	m_boundingBox[15].z = zh;

	char* m_name = new char[m_header.namelength + 1];
	ZeroMemory(m_name, m_header.namelength + 1);
	file.Read(m_header.nameoffset, m_header.namelength, m_name);
	m_modelName = m_name;
	delete [] m_name;

	if(!noCache)
	{
		M2ModelCache* c = sM2Cache.GetCache(m_modelName);
		if(c && false)
		{
			LoadCached(c);
			return true;
		}
	}

	M2Vertex* vertices = (M2Vertex*)(file.GetPointer() + m_header.ofsVertices);
	Texture** basetex = new Texture*[m_header.nTextures];
	M2ModelTexture* textures = (M2ModelTexture*)(file.GetPointer() + m_header.ofsTextures);
	std::vector<std::string> texts;
	for(ui32 i = 0; i < m_header.nTextures; ++i)
	{
		char name[255];
		strncpy_s(name, (char*)file.GetPointer() + (textures + i)->nameOfs, (textures + i)->nameLen);
		name[(textures + i)->nameLen] = 0;
		basetex[i] = CTextureHandler::GetInstance()->GetTexture(name);
		texts.push_back(name);
	}
	size_t t = m_fileName.rfind("\\");
	std::string rootPath;
	if(t)
		rootPath = m_fileName.substr(0, t);
	else
		rootPath = m_fileName;
	char skinPath[255];
	sprintf_s<255>(skinPath, "%s\\%s00.skin", rootPath.c_str(), m_modelName.c_str());
	CMpqFile skin(skinPath);
	if(!skin.IsOpened())
		return false;
	SKINView m_view;
	skin.Read(0, sizeof(m_view), &m_view);
	m_nMeshes = m_view.nTextureUnits;
	m_meshes = new Mesh[m_nMeshes];
	ui8* data = new ui8[skin.GetSize()];
	skin.Read(0, skin.GetSize(), data);
	ui16 nTIndices = m_view.nTriangles;
	ui16 nIndices = m_view.nIndices;
	ui16* indexLookup = (ui16*)(data + m_view.ofsIndices);
	ui16* indices = new ui16[nTIndices];
	ui16* triangles = (ui16*)(data + m_view.ofsTriangles);

	SKINSubMesh* m_submeshes = (SKINSubMesh*)(data + m_view.ofsSubMeshes);
	SKINTexUnit* m_texunits = (SKINTexUnit*)(data + m_view.ofsTextureUnits);

	ui16* texLookUp = (ui16*)(file.GetPointer() + m_header.ofsTexLookUp);
	ui16* texAnimLookUp = (ui16*)(file.GetPointer() + m_header.ofsTexAnimLookUps);
	ui16* texUnitLookUp = (ui16*)(file.GetPointer() + m_header.ofsTexUnits);

	m_textures = new Texture*[m_view.nTextureUnits];
	for(ui32 i = 0; i < m_view.nTextureUnits; ++i)
		m_textures[i] = 0;

	for(ui32 i = 0; i < nTIndices; ++i)
	{
		indices[i] = indexLookup[triangles[i]];
	}

	M2ModelCache* ca = new M2ModelCache;
	ca->references = 1;
	ca->szModelName = m_modelName;
	ca->vertices = new Vertex3FT*[m_view.nTextureUnits];
	ca->nVertices = new ui32[m_view.nTextureUnits];
	for(ui32 i = 0; i < m_view.nTextureUnits; ++i)
		ca->vertices[i] = 0;
	
	ca->textures = texts;
	ca->nPasses = m_view.nTextureUnits;

	for(ui32 i = 0; i < m_view.nTextureUnits; ++i)
	{
		M2ModelPass pa;
		pa.usetex2 = false;
		pa.texture2 = 0;
		ui32 gs = m_texunits[i].SubMeshIndex_01;
		pa.indexStart = m_submeshes[gs].startTriangle;
		pa.indexCount = m_submeshes[gs].nTriangles;
		pa.vertexStart = m_submeshes[gs].startVertex;
		pa.vertexEnd = m_submeshes[gs].nVertices + pa.vertexStart;
		pa.order = m_texunits[gs].shading;
		m_textures[i] = basetex[texLookUp[m_texunits[i].Texture]];
		Vertex3FT* tmpvertices = new Vertex3FT[pa.indexCount];

		for(ui32 t = pa.indexStart, k = 0; k < pa.indexCount; ++t, ++k)
		{
			ui16 ind = indices[t];
			tmpvertices[k].x = vertices[ind].x;
			tmpvertices[k].y = vertices[ind].z;
			tmpvertices[k].z = vertices[ind].y;
			tmpvertices[k].u = vertices[ind].texturecoords[0];
			tmpvertices[k].v = vertices[ind].texturecoords[1];
		}
		m_meshes[i].Load(tmpvertices, VERTEX3FT, pa.indexCount, pa.indexCount / 3);
		ca->vertices[i] = tmpvertices;
		ca->nVertices[i] = pa.indexCount;
	}

	if(!noCache)
		sM2Cache.AddCache(m_modelName, ca);
	else
		delete ca;
	
	delete [] data;
	delete [] indices;
	if(!noCache)
		sD3D.AddMesh(this);
	return true;
}


void M2Model::Draw(Vertex3F pos, Vertex3F tar)
{
	if(m_error || !sSelection.GetDrawMDX())
		return;

	Vertex3F front(tar.x - pos.x, tar.y - pos.y, tar.z - pos.z);
	Vertex3F target(translation[0] - pos.x, translation[1] - pos.y, translation[2] - pos.z);
	Vertex3F target2D(translation[0] - pos.x, 0.0f, translation[2] - pos.z);	
	float angle = acos((front.x * target.x + front.z * target.z + front.y * target.y) / (front.Length() * target.Length()));
	if((angle * 180 / M_PI > 50 && target2D.Length() > CHUNKSIZE))
		return;

	float radius = sqrtf(powf(pos.x - translation[0], 2.0f) + powf(pos.z - translation[2], 2.0f));
	if(radius > 0.7f * TILESIZE)
		return;

	IDirect3DDevice9* pDev = sD3D;
	pDev->SetRenderState(D3DRS_ALPHAREF, FToDW(0.001f));
	pDev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); 
	pDev->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
	D3DXMATRIX matTrans, matScale, matRot;
	D3DXMatrixTranslation(&matTrans, translation[0], translation[1], translation[2]);
	D3DXMatrixScaling(&matScale, scaling[0], scaling[1], scaling[2]);
	D3DXMatrixRotationY(&matRot, rotation[1]);
	D3DXMATRIX matWorld;
	D3DXMatrixIdentity(&matWorld);
	matWorld = matRot * matScale * matTrans;
	sD3D->SetTransform(D3DTS_WORLD, &matWorld);
	for(ui32 i = 0; i < m_nMeshes; ++i)
	{
		if(m_textures[i])
		{
			sD3D->SetTexture(0, *m_textures[i]);
			//sD3D->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
		}
		m_meshes[i].Draw();
	}

	if(m_selected)
	{
		D3DXMATRIX view, proj;
		sD3D->GetTransform(D3DTS_VIEW, &view);
		sD3D->GetTransform(D3DTS_PROJECTION, &proj);
		D3DXMATRIX viewProj = matWorld * view * proj;
		gpLine->DrawTransform(m_boundingBox, 16, &viewProj, D3DCOLOR_XRGB(255, 255, 255));
	}
	sD3D->SetTexture(0, 0);
	sD3D->SetTransform(D3DTS_WORLD, &matID);

	pDev->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
	pDev->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
}

bool M2Model::Intersect(Vertex2F& mouse, float* pOut)
{
	if(m_error || !sSelection.GetDrawMDX())
		return false;

	Vertex3F pos = sCamera.GetCamPosition();
	Vertex3F tar = sCamera.GetTargetPosition();

	Vertex3F front(tar.x - pos.x, tar.y - pos.y, tar.z - pos.z);
	Vertex3F target(translation[0] - pos.x, translation[1] - pos.y, translation[2] - pos.z);
	Vertex3F target2D(translation[0] - pos.x, 0.0f, translation[2] - pos.z);	
	float angle = acos((front.x * target.x + front.z * target.z + front.y * target.y) / (front.Length() * target.Length()));
	if((angle * 180 / M_PI > 50 && target2D.Length() > CHUNKSIZE))
		return false;

	float radius = sqrtf(powf(pos.x - translation[0], 2.0f) + powf(pos.z - translation[2], 2.0f));
	if(radius > 0.7f * TILESIZE)
		return false;

	D3DXMATRIX matTrans, matScale;
	D3DXMatrixTranslation(&matTrans, translation[0], translation[1], translation[2]);
	D3DXMatrixScaling(&matScale, scaling[0], scaling[1], scaling[2]);
	D3DXMATRIX matWorld;
	D3DXMatrixIdentity(&matWorld);
	matWorld = matScale * matTrans;

	D3DXVECTOR3 v, rayDir, rayOrigin;
	D3DXMATRIX mat, m;

	sD3D->GetTransform(D3DTS_PROJECTION, &mat);

	D3DXMatrixInverse(&m, 0, &sCamera.GetMatView());
	POINT bufferSize = sD3D.GetSize();

	v.x = (((2.0f * mouse.x) / bufferSize.x) - 1) / mat._11;
	v.y = -(((2.0f * mouse.y) / bufferSize.y) - 1) / mat._22;
	v.z = 1.0f;

	rayDir.x  = v.x * m._11 + v.y * m._21 + v.z * m._31;
	rayDir.y  = v.x * m._12 + v.y * m._22 + v.z * m._32;
	rayDir.z  = v.x * m._13 + v.y * m._23 + v.z * m._33;

	rayOrigin.x = m._41;
	rayOrigin.y = m._42;
	rayOrigin.z = m._43;

	D3DXMatrixInverse(&m, 0, &matWorld);

	D3DXVec3TransformCoord(&rayOrigin, &rayOrigin, &m);
	D3DXVec3TransformNormal(&rayDir, &rayDir, &m);

	D3DXVec3Normalize(&rayDir, &rayDir);

	bool intersected = false;
	float curDist = 99999.0f;
	for(ui32 i = 0; i < m_nMeshes; ++i)
	{
		float tmp = 0.0f;
		if(m_meshes[i].Intersect(rayOrigin, rayDir, tmp))
		{
			if(tmp < curDist)
				curDist = tmp;
			intersected = true;
		}
	}
	*pOut = curDist;
	return intersected;
}

void M2Model::ModTranslation(float x, float y, float z)
{
	translation[0] += x;
	translation[1] += y;
	translation[2] += z;
	if(abs(x) >= 0.1f || abs(z) >= 0.1f)
		sD3D.UpdateMDXPosition(m_pSpawnMap, m_spawnEntry, x, y, z);
}